﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Reflection;
using System.ComponentModel;

namespace Sims3Package.Wrapper
{
    public class CASPResourceWrapper :Wrapper
    {
        public new static List<UInt32> ResourceTypes { get { return new List<uint>(mResourceTypes); } }
        private new static UInt32[] mResourceTypes = { 0x034AEECBU };

        public CASPResourceWrapper(Stream s) : base(s) { stream = s; if (stream == null) return; stream.Position = 0; Parse(); }
        public int Version { get; private set; }
        public int Offset { get; private set; }
        public List<Preset> PresetList { get; set; }
        public string Unknown1 { get; set; }
        public Single SortPriority { get; set; }
        public byte UnKnown2 { get; set; }
        public ClothingTypes ClothingType { get; set; }
        public DataType dataType { get; set; }
        public CASAgeGenderFlags Age { get; set; }
        public CASAgeGenderFlags Gender { get; set; }
        public CASAgeGenderFlags Species { get; set; }
        public CASAgeGenderFlags Handiness { get; set; }
        public OutfitCategories OutfitType { get; set; }
        public byte NakedReference { get; private set; }
        public byte ParentReference { get; private set; }
        public byte FatReference { get; private set; }
        public byte FitReference { get; private set; }
        public byte ThinReference { get; private set; }
        public byte SpecialReference { get; private set; }
        public UInt32 Unknown3 { get; set; }
        public List<byte> VPXYReferenceList { get; set; }
        public List<LOD> LODReferenceList { get; set; }
        public List<byte> DiffuseReferenceList { get; set; }
        public List<byte> SpecularReferenceList { get; set; }
        public List<byte> Diffuse2ReferenceList { get; set; }
        public List<byte> Specular2ReferenceList { get; set; }
        public List<byte> BoneDeltaList { get; set; }
        public string ShoeMaterial { get; set; }
        public List<TGIBlock> TGIReferenceList { get; set; }

        private void Parse()
        {
            BinaryReader r = new BinaryReader(stream);
            this.Version = r.ReadInt32();
            this.Offset = r.ReadInt32();
            // Preset
            int presetListCount = r.ReadInt32();
            this.PresetList = new List<Preset>(presetListCount);
            for (int i = 0; i < presetListCount; i++)
            {
                string s = Encoding.Unicode.GetString(r.ReadBytes(r.ReadInt32() * 2));
                this.PresetList.Add(new Preset(s, r.ReadUInt32()));
            }

            this.Unknown1 = Support.BigEndianString.Read(stream, Encoding.BigEndianUnicode);
            this.SortPriority = r.ReadSingle();
            this.UnKnown2 = r.ReadByte();
            this.ClothingType = (ClothingTypes)r.ReadUInt32();
            this.dataType = (DataType)r.ReadUInt32();

            uint agegenderspecies = r.ReadUInt32();
            this.Age = (CASAgeGenderFlags)(agegenderspecies & (uint)MaskFlags.AgeMask);
            this.Gender = (CASAgeGenderFlags)(agegenderspecies & (uint)MaskFlags.GenderMask);
            this.Species = (CASAgeGenderFlags)(agegenderspecies & (uint)CASAgeGenderFlags.SpeciesMask);
            this.Handiness = (CASAgeGenderFlags)(agegenderspecies & (uint)MaskFlags.HandednessMask);
            this.OutfitType = (OutfitCategories)r.ReadUInt32();

            this.NakedReference = r.ReadByte();
            this.ParentReference = r.ReadByte();
            this.FatReference = r.ReadByte();
            this.FitReference = r.ReadByte();
            this.ThinReference = r.ReadByte();
            this.SpecialReference = r.ReadByte();

            this.Unknown3 = r.ReadUInt32();

            byte vpxyCount = r.ReadByte(); this.VPXYReferenceList = new List<byte>(vpxyCount);
            for (int i = 0; i < vpxyCount; i++) this.VPXYReferenceList.Add(r.ReadByte());

            byte assetCount = r.ReadByte(); this.LODReferenceList = new List<LOD>(assetCount);
            for (int i = 0; i < assetCount; i++) this.LODReferenceList.Add(new LOD(stream));

            byte diffuseCount = r.ReadByte(); this.DiffuseReferenceList = new List<byte>(diffuseCount);
            for (int i = 0; i < diffuseCount; i++) this.DiffuseReferenceList.Add(r.ReadByte());
            byte specularCount = r.ReadByte(); this.SpecularReferenceList = new List<byte>(specularCount);
            for (int i = 0; i < specularCount; i++) this.SpecularReferenceList.Add(r.ReadByte());
            byte diffuse2Count = r.ReadByte(); this.Diffuse2ReferenceList = new List<byte>(diffuse2Count);
            for (int i = 0; i < diffuse2Count; i++) this.Diffuse2ReferenceList.Add(r.ReadByte());
            byte specular2Count = r.ReadByte(); this.Specular2ReferenceList = new List<byte>(specular2Count);
            for (int i = 0; i < specular2Count; i++) this.Specular2ReferenceList.Add(r.ReadByte());
            byte boneDeltaCount = r.ReadByte(); this.BoneDeltaList = new List<byte>(boneDeltaCount);
            for (int i = 0; i < boneDeltaCount; i++) this.BoneDeltaList.Add(r.ReadByte());

            this.ShoeMaterial = Support.BigEndianString.Read(stream, Encoding.BigEndianUnicode);
            if (stream.Position != this.Offset + 8) throw new InvalidDataException("TGI List position is not correct");
            byte TGICount = r.ReadByte(); this.TGIReferenceList = new List<TGIBlock>(TGICount);
            for (int i = 0; i < TGICount; i++) this.TGIReferenceList.Add(new TGIBlock(stream, TGIOrder.IGT));

        }

        public override Stream UnParse()
        {
            long position = 0;
            MemoryStream s = new MemoryStream();
            BinaryWriter w = new BinaryWriter(s);
            w.Write(Version);
            position = s.Position;
            w.Write(0);
            w.Write(this.PresetList != null ? this.PresetList.Count : 0);   // Preset count
            foreach (Preset preset in this.PresetList) preset.UnParse(s);   // Preset List
            Support.BigEndianString.Write(s, this.Unknown1);

            w.Write(this.SortPriority);
            w.Write(this.UnKnown2);
            w.Write((uint)this.ClothingType);
            w.Write((uint)dataType);
            w.Write((uint)(this.Age | this.Species | this.Gender | this.Handiness));
            w.Write((uint)this.ClothingType);

            w.Write(this.NakedReference);
            w.Write(this.ParentReference);
            w.Write(this.FatReference);
            w.Write(this.FitReference);
            w.Write(this.ThinReference);
            w.Write(this.SpecialReference);

            w.Write(this.Unknown3);

            w.Write((byte)this.VPXYReferenceList.Count); foreach (byte b in this.VPXYReferenceList) w.Write(b);
            w.Write((byte)this.LODReferenceList.Count); foreach (LOD lod in this.LODReferenceList) lod.UnParse(s);
            w.Write((byte)this.DiffuseReferenceList.Count); foreach (byte b in this.DiffuseReferenceList) w.Write(b);
            w.Write((byte)this.SpecularReferenceList.Count); foreach (byte b in this.SpecularReferenceList) w.Write(b);
            w.Write((byte)this.Diffuse2ReferenceList.Count); foreach (byte b in this.Diffuse2ReferenceList) w.Write(b);
            w.Write((byte)this.Specular2ReferenceList.Count); foreach (byte b in this.Specular2ReferenceList) w.Write(b);
            w.Write((byte)this.BoneDeltaList.Count); foreach (byte b in this.BoneDeltaList) w.Write(b);

            Support.BigEndianString.Write(s, this.ShoeMaterial);

            long tgiposition = s.Position;
            s.Position = position;
            w.Write(tgiposition - 8);
            s.Position = tgiposition;

            w.Write((byte)this.TGIReferenceList.Count); foreach (TGIBlock tgi in this.TGIReferenceList) tgi.UnParse(s);
            

            return s;
        }

        public override string Value
        {
            get
            {
                StringBuilder sb = new StringBuilder();
                sb.AppendFormat("Version: 0x{0:X8}\n", this.Version);
                sb.AppendFormat("XML Preset List: Count 0x{0:X8}", this.PresetList.Count).AppendLine();
                foreach (Preset preset in this.PresetList) sb.AppendFormat("{0}...", preset.PresetXml.Substring(0, 100)).AppendLine();
                sb.AppendLine().AppendFormat("Unknown1: {0}", Unknown1).AppendLine().AppendFormat("Sort Priority: {0}", SortPriority).AppendLine();
                sb.AppendFormat("Unknown2: 0x{0:X2}", UnKnown2).AppendLine();
                sb.AppendFormat("Clothing Type: {0}", this.ClothingType).AppendLine();
                sb.AppendFormat("Data Type: {0}", this.dataType).AppendLine();
                sb.AppendFormat("Age: {0};  Gender: {1};    Species: {2};   Handiness: {3}", this.Age, Support.EnumString<CASAgeGenderFlags>(this.Gender), this.Species, this.Handiness).AppendLine();
                sb.AppendFormat("Outfit Category: {0}", Support.EnumString<OutfitCategories>(this.OutfitType)).AppendLine().AppendLine();

                sb.AppendLine().AppendFormat("CAS Part Naked Reference: {0}", TGIReferenceList[NakedReference]).AppendLine();
                sb.AppendFormat("CAS Part Parent Reference: {0}", TGIReferenceList[ParentReference]).AppendLine();
                sb.AppendFormat("CAS Blend Info Fat Reference: {0}", TGIReferenceList[FatReference]).AppendLine();
                sb.AppendFormat("CAS Blend Info Fit Reference: {0}", TGIReferenceList[FitReference]).AppendLine();
                sb.AppendFormat("CAS Blend Info Thin Reference: {0}", TGIReferenceList[ThinReference]).AppendLine();
                sb.AppendFormat("CAS Blend Info Special Reference: {0}", TGIReferenceList[SpecialReference]).AppendLine();

                sb.AppendFormat("Unknown3: 0x{0:X8}", this.Unknown3).AppendLine().AppendLine();

                // Reflection here. Yes, I'm lazy. I'll write a string function via reflection as peter did in the future :p
                foreach (PropertyInfo p in this.GetType().GetProperties().Where(property => property.PropertyType == typeof(List<byte>)))
                {
                    List<byte> list = p.GetValue(this, null) as List<byte>;
                    if (list != null && list.Count > 0)
                    {
                        sb.AppendFormat("{0}: Count 0x{1:X2}", p.Name, list.Count).AppendLine();
                        for (int i = 0; i < list.Count; i++) sb.AppendFormat("0x{0:X2}: {1}", i, TGIReferenceList[list[i]]).AppendLine();
                        sb.AppendLine();
                    }
                }

                sb.AppendFormat("LOD List: Count 0x{0:X2}", this.LODReferenceList.Count).AppendLine();
                for (int i = 0; i < this.LODReferenceList.Count; i++) sb.AppendFormat("0x{0:X2}: {1}", i, LODReferenceList[i].Value).AppendLine();

                sb.AppendFormat("Shoe Material: {0}", this.ShoeMaterial).AppendLine().AppendLine();

                sb.AppendFormat("TGI Reference List: Count 0x{0:X2}", (byte)this.TGIReferenceList.Count).AppendLine();
                for(int i =0; i < this.TGIReferenceList.Count; i++) sb.AppendFormat("0x{0:X2}: {1}", i, this.TGIReferenceList[i]).AppendLine();
                return sb.ToString();
            }
        }


        #region Sub-Type
        public class Preset
        {
            public string PresetXml { get; set; }
            public uint PresetID { get; set; }
            public Preset(string xml, uint id)
            {
                this.PresetXml = xml; this.PresetID = id;
            }
            public Preset()
            {
                this.PresetXml = string.Empty;
                this.PresetID = 0;
            }


            public void UnParse(Stream s)
            {
                BinaryWriter w = new BinaryWriter(s);
                w.Write(PresetXml.Length);
                w.Write(Encoding.Unicode.GetBytes(PresetXml));
                w.Write(PresetID);
            }
        }

        public class LOD
        {
            public byte Level { get; set; }
            public uint DestTexture { get; set; }
            public List<Asset> AssetList { get; set; }

            public LOD(Stream s)
            {
                BinaryReader r = new BinaryReader(s);
                this.Level = r.ReadByte();
                this.DestTexture = r.ReadUInt32();
                int count = r.ReadByte();
                this.AssetList = new List<Asset>(count);
                for (int i = 0; i < count; i++) this.AssetList.Add(new Asset(s));
            }
            public LOD()
            {
                this.Level = 0;
                this.DestTexture = 0;
                this.AssetList = new List<Asset>();
            }

            public void UnParse(Stream s)
            {
                BinaryWriter w = new BinaryWriter(s);
                w.Write(this.Level);
                w.Write(this.DestTexture);
                w.Write((byte)this.AssetList.Count);
                foreach (Asset asset in this.AssetList) asset.Unparse(s);
            }

            public string Value
            {
                get
                {
                    StringBuilder b = new StringBuilder();
                    b.AppendFormat("Level: 0x{0:X2}, DestTexture: 0x{1:X8}", this.Level, this.DestTexture).AppendLine();
                    b.AppendFormat("Asset List: Count 0x{0:X2}", this.AssetList.Count).AppendLine();
                    foreach (Asset a in this.AssetList) b.Append(a.Value);
                    return b.ToString();
                }
            }


            public class Asset
            {
                public CASGeomFlags Sorting { get; set; }
                public CASGeomFlags SpecLevel { get; set; }
                public CASGeomFlags CastShadow { get; set; }

                public Asset(Stream s)
                {
                    BinaryReader r = new BinaryReader(s);
                    this.Sorting = (CASGeomFlags)r.ReadUInt32();
                    this.SpecLevel = (CASGeomFlags)r.ReadUInt32();
                    this.CastShadow = (CASGeomFlags)r.ReadUInt32();
                }

                public Asset()
                {
                    this.Sorting = 0;
                    this.SpecLevel = 0;
                    this.CastShadow = 0;
                }

                public void Unparse(Stream s)
                {
                    BinaryWriter w = new BinaryWriter(s);
                    w.Write((uint)this.Sorting);
                    w.Write((uint)this.SpecLevel);
                    w.Write((uint)this.CastShadow);
                }
                public string Value
                {
                    get { return string.Format("Sorting: {0}, SpecLevel {1}, CastShow {2}\n", this.Sorting, this.SpecLevel, this.CastShadow); }
                }

            }

        }

        #region Flags

        private enum MaskFlags : uint
        {
            AgeMask = CASAgeGenderFlags.Elder | CASAgeGenderFlags.Adult | CASAgeGenderFlags.YoungAdult | CASAgeGenderFlags.Teen |
                CASAgeGenderFlags.Child | CASAgeGenderFlags.Toddler | CASAgeGenderFlags.Baby,
            GenderMask = CASAgeGenderFlags.Female | CASAgeGenderFlags.Male,
            HandednessMask = CASAgeGenderFlags.RightHanded | CASAgeGenderFlags.LeftHanded
        }

        [Flags]
        public enum CASAgeGenderFlags : uint
        {
            None = 0U,
            Baby = 1U,
            Toddler = 2U,
            Child = 4U,
            Teen = 8U,
            YoungAdult = 16U,
            Adult = 32U,
            Elder = 64U,
            Male = 4096U,
            Female = 8192U,
            Human = 256U,
            Horse = 512U,
            Cat = Horse | Human,
            Dog = 1024U,
            LittleDog = Dog | Human,
            Deer = Dog | Horse,
            Raccoon = Deer | Human,
            LargeBird = 2048U,
            SimWalkingDog = LargeBird | Human,
            SimWalkingLittleDog = LargeBird | Horse,
            SimLeadingHorse = SimWalkingLittleDog | Human,
            Paddleboat = LargeBird | Dog,
            WaterScooter = Paddleboat | Human,
            Speedboat = Paddleboat | Horse,
            Rowboat = Speedboat | Human,
            HouseboatSmall = 16640U,
            HouseboatMedium = 16896U,
            HouseboatLarge = HouseboatMedium | Human,
            Shark = 17408U,
            Sailboat = Shark | Human,
            WindsurfBoard = Shark | Horse,
            SpeciesMask = 52992U,
            LeftHanded = 1048576U,
            RightHanded = 2097152U,
        }

        

        public enum ClothingTypes : uint    // BodyType in Sims 3 code
        {
            None = 0U,
            Hair = 1U,
            Scalp = 2U,
            Face = 3U,
            FullBody = 4U,
            UpperBody = 5U,
            LowerBody = 6U,
            Shoes = 7U,
            Accessories = 8U,
            FirstAccessories = 8U,
            Necklace = 9U,
            NoseRing = 10U,
            Earrings = 11U,
            Glasses = 12U,
            Bracelet = 13U,
            Ring = 14U,
            WeddingRing = 15U,
            Beard = 16U,
            FirstFace = 17U,
            Lipstick = 17U,
            EyeShadow = 18U,
            EyeLiner = 19U,
            Blush = 20U,
            CostumeMakeup = 21U,
            Eyebrows = 22U,
            EyeColor = 23U,
            Gloves = 24U,
            Socks = 25U,
            Mascara = 26U,
            Moles = 27U,
            Freckles = 28U,
            AgeWeathering = 29U,
            LeftEarring = 30U,
            RightEarring = 31U,
            Armband = 32U,
            Tattoo = 33U,
            TattooTemplate = 34U,
            Dental = 35U,
            LeftGarter = 36U,
            RightGarter = 37U,
            BirthMark = 38U,
            BodyHairUpperChest = 39U,
            BodyHairStomach = 40U,
            BodyHairLowerBack = 41U,
            BodyHairUpperBack = 42U,
            BodyHairFullBack = 43U,
            BodyHairForearms = 44U,
            BodyHairCalves = 45U,
            BodyHairFeet = 46U,
            PetBody = 47U,
            PetTail = 48U,
            PetEars = 49U,
            PetMane = 50U,
            PetBridle = 51U,
            PetCollar = 51U,
            PetSaddle = 52U,
            PeltLayer = 53U,
            BasePeltLayer = 54U,
            PetHooves = 55U,
            PetHorn = 56U,
            PetBlanket = 57U,
            PetBreastCollar = 58U,
            PetBeard = 59U,
            Last = 60U,
        }

        public enum DataType : uint
        {
            Hair = 0x00000001U,
            Scalp = 0x00000002,
            FaceOverlay = 0x00000004,
            Body = 0x00000008,
            Accessory = 0x00000010
        }

        [Flags]
        public enum OutfitCategories : uint
        {
            None = 0U,
            Naked = 1U,
            Everyday = 2U,
            Formalwear = 4U,
            Sleepwear = 8U,
            Swimwear = 16U,
            Athletic = 32U,
            Singed = 64U,
            MartialArts = 128U,
            Career = 256U,
            PrimaryCategories = 318U,
            AuxCareer = 512U,
            Makeover = 1024U,
            SkinnyDippingTowel = 2048U,
            Racing = 4096U,
            Jumping = 8192U,
            Bridle = 16384U,
            PrimaryHorseCategories = 28674U,
            Massage = 32768U,
            ChildImagination = 65536U,
            Special = 131072U,
            Outerwear = 262144U,
            Supernatural = 524288U,
            CategoryMask = 1048575U,
            All = 4294967295U,
        }

        public enum CASGeomFlags : uint
        {
            None = 0U,
            Mergable = 1U,
            IncludeMorphs = 2U,
            IncludeTweaks = 4U,
            IncludeTangents = 8U,
            FourBoneSkinning = 16U,
            TwoBoneSkinning = 32U,
            TwoQuatSkinning = 64U,
            OneQuatSkinning = 128U,
            SpecLevel0 = 256U,
            SpecLevel1 = 512U,
            SpecLevel2 = 1024U,
            SpecLevel3 = 2048U,
            SpecLevel4 = 4096U,
            SpecLevel5 = 8192U,
            Sorted = 16384U,
            ShadowCaster = 32768U,
            Mask = 65535U,
        }

        #endregion

        

        #endregion
    }
}
